#!/usr/bin/python
# -*- coding: UTF-8 -*-

"""
@company:ZYF
@author:yu.qian
@file:decorators.py
@time:2022/10/28
"""

import re
import functools
import traceback
import json
import logging
from typing import Union
import werkzeug.exceptions
from jwt import ExpiredSignatureError, InvalidSignatureError, ImmatureSignatureError, DecodeError

import dtcloud
from dtcloud.http import request, Response
from dtcloud.tools import date_utils

# dtcloud3.0
from dtcloud.http import JsonRPCDispatcher

# request_type = request.dispatcher.routing_type

from dtcloud.tools import jwt_utils
from commons.exceptions.errors import ApiError, AppAuthorizationError as AuthorizationError

logger = logging.getLogger(__name__)


class ResultData:
    """
    数据结果
    """

    default_msg = '接口响应成功'

    def __init__(self, code=0, msg=None, **kwargs):
        self.status = code
        self.code = code
        self.msg = msg or self.default_msg
        self._data = kwargs

    @property
    def data(self):
        return {
            'status': self.status,
            'code': self.code,
            'data': self._data,
            'msg': self.msg,
        }

    def __setitem__(self, key, value):
        self._data[key] = value

    def __repr__(self):
        return str(self.data)

    def __str__(self):
        return str(self.data)


def _json_response(self, result=None, error=None):
    """
    改写JsonRequest的_json_response方法,使用于dtcloud2.0
    """
    json_ = self.endpoint.routing.get('json')
    if json_:
        response = {}
        if error is not None:
            response['error'] = error
        if result is not None:
            response = result
        mime = 'application/json; charset=UTF-8'
    else:
        response = {
            'jsonrpc': '2.0',
            'id': self.jsonrequest.get('id')
        }
        if error is not None:
            response['error'] = error
        if result is not None:
            response['result'] = result
        mime = 'application/json'

    body = json.dumps(response, default=date_utils.json_default)
    return Response(
        body, status=error and error.pop('http_status', 200) or 200,
        headers=[('Content-Type', mime), ('Access-Control-Allow-Origin', '*'), ('Content-Length', len(body))]
    )


def _response(self, result=None, error=None):
    """
    改写JsonRPCDispatcher的_response方法,使用于dtcloud3.0
    """
    ir_http = self.request.registry['ir.http']

    try:
        rule, args = ir_http._match(self.request.httprequest.path)
    except werkzeug.exceptions.NotFound:
        self.params = self.get_http_params()
        response = ir_http._serve_fallback()
        if response:
            self.dispatcher.post_dispatch(response)
            return response
        raise
    if rule.endpoint and rule.endpoint.routing.get('json'):
        response = {}
        if error is not None:
            response['error'] = error
        if result is not None:
            response = result
    else:
        response = {
            'jsonrpc': '2.0',
            'id': self.jsonrequest.get('id')
        }
        if error is not None:
            response['error'] = error
        if result is not None:
            response['result'] = result

    return self.request.make_json_response(response)


# dtcloud3.0
# setattr(JsonRPCDispatcher, '_response', _response)


# dtcloud2.0
# setattr(JsonRequest, '_json_response', _json_response)

def exc_result(exc, msg=None, code=500):
    name = type(exc).__name__
    module = type(exc).__module__
    result = {
        "code": code,
        "data": {
            "name": f'{module}.{name}' if module else name,
            "arguments": exc.args,
            "traceback": traceback.format_exc().splitlines(),
        },
        "msg": str(exc) if msg is None else msg,

    }
    return result


def render_json(func):
    """
    装饰器：渲染需要的 JSON 数据的api
    """

    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        data = None
        try:
            result_data: ResultData = func(self, *args, **kwargs)
            if not isinstance(result_data, ResultData):
                raise ValueError('Request handler return value must be type of class ResultData.')
            data = result_data.data
        except ApiError as ae:
            logger.exception('%s:%s' % (ae.code, ae.msg))
            data = ae.data
        except Exception as exc:
            result = exc_result(exc, code=500)
            logger.exception(result)
            data = result
        finally:
            if not data:
                data = {'code': -1, 'msg': 'Request handler no return value.'}
            else:
                if not isinstance(data, dict):
                    try:
                        raise ValueError('Request handler return value type error.')
                    except ValueError:
                        logger.error(traceback.format_exc())
                    data = {'code': -1, 'msg': 'Request handler return value type error.'}

        if 'self' in data:
            del data['self']

        if request.dispatcher.routing_type == 'http':
            # 头里面加入Access-Control-Allow-Origin用于处理跨域问题
            headers = [('Access-Control-Allow-Origin', '*')]
            response = request.make_json_response(data, headers=headers)
            return response

        return data

    return wrapper


def check_jwt_header(token):
    token_header = {}
    token_payload = {}
    try:
        header, payload = jwt_utils.decode(token)
        logger.debug('Token, HEADER=%s, PAYLOAD=%s' % (json.dumps(header), json.dumps(payload)))
        if header:
            token_header.update(header)
            del token_header['typ']
            del token_header['alg']
        if payload:
            token_payload.update(payload)
            del token_payload['exp']
            del token_payload['iat']
            del token_payload['nbf']
        request.update_env(payload['uid'])
        # 判断uid是否在res.users中存在
        if request.env['res.users'].browse(payload['uid']).exists():
            return token_header, token_payload
        else:
            raise AuthorizationError(AuthorizationError.InvalidToken, 'Token uid is not exist.')
    except InvalidSignatureError:
        raise AuthorizationError(AuthorizationError.InvalidToken, 'Invalid Token.')
    except DecodeError:
        raise AuthorizationError(AuthorizationError.InvalidToken, 'Invalid Token.')
    except ExpiredSignatureError:
        raise AuthorizationError(AuthorizationError.ExpireToken, 'Token expire date.')
    except ImmatureSignatureError:
        raise AuthorizationError(AuthorizationError.InvalidSign, 'Immature signature.')


def permission_required(permission_code: Union[str, list, tuple]):
    """
    校验权限

    :param permission_code: 权限编码
    :return:
    """

    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kwargs):
            authorization = request.httprequest.headers.get("Authorization")
            if not authorization:
                raise AuthorizationError(100, 'Permission denied.')
            if authorization.lower().startswith('bearer'):
                # 去掉bearer
                authorization = re.findall(r'Bearer (.*)', authorization)[0]
            token_header, token_payload = check_jwt_header(authorization)
            uid = token_payload.get("uid")
            if not uid:
                raise AuthorizationError(100, 'Permission denied.')
            # 将解析出来的uid赋值给request
            request.update_env(uid)
            current_user = request.env['res.users'].sudo().browse(uid).exists()
            if not current_user:
                raise AuthorizationError(100, 'Permission denied.')
            passed = current_user.has_perm(permission_code)
            if not passed:
                raise AuthorizationError(100, 'Permission denied.')
            return func(self, *args, **kwargs)

        return wrapper

    return decorator


content_type_dict = {
    'pdf': 'application/pdf',
    'png': 'image/png',
    'ppt': 'application/vnd.ms-powerpoint',
    'txt': 'text/plain',
    'xls': 'application/vnd.ms-excel',
    'xlsx': 'application/vnd.ms-excel',
    'gif': 'image/gif',
    'jpg': 'image/jpeg',
    'jpeg': 'image/jpeg',
}


def render_file(func):
    import urllib.parse
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        data = None
        try:
            result_data = func(self, *args, **kwargs)
            if isinstance(result_data, tuple) and len(result_data) > 1:
                file_name = result_data[0]
                data = result_data[1]
            else:
                file_name = 'index'
                for part in reversed(self.request.uri.split('/')):
                    if part:
                        file_name = part
                        break
                data = result_data
        except ApiError as ae:
            data = ae.data
        except Exception:
            data = {'code': -1, 'msg': 'Request handler no return value.'}
            logger.exception(self.request.body)
        finally:
            if isinstance(data, dict):
                if 'self' in data:
                    del data['self']
                response = request.make_json_response(data)
                return response
            else:
                parts = file_name.split('.')
                suffix = parts[-1] if len(parts) > 1 else None
                content_type = content_type_dict.get(suffix, 'application/octet-stream')
                file_name = urllib.parse.quote(file_name)
                headers = [("Content-Disposition", f'attachment;filename={file_name}'),
                           ("Content-Type", f'{content_type};content-type=utf-8')]
                response = request.make_response(data, headers=headers)
            return response

    return wrapper
